#include "../hdr/b64_encode.h"

/*  Estructura que almacena informacion sobre los datos
	codificados para ser escritos en el archivo de salida. */
typedef struct {
	unsigned char buffer;
	unsigned char index;
	unsigned long fileLength;
} buffer;

/*  Estructura que almacena tres bytes que seran leidos
	del archivo de entrada. */
typedef struct {
	unsigned char a, b, c;
} encode_block;

void init_encode_block(encode_block* b) 
{
	b->a = '\0';
	b->b = '\0';
	b->c = '\0';
}

void init_buffer(buffer* buff) 
{
	buff->buffer = 0;
	buff->index = 0;
}

/*  Lee hasta tres bytes del archivo de entrada y los almacena en 
	el bloque de lectura. Devuelve la cantidad de bytes leidos o 
	-1 en caso de que no se haya podido leer ninguno. */
int read_from_input(encode_block* b, FILE* input) 
{
	int count = 0;
	init_encode_block(b);
	if (!feof(input)) 
	{
		count = fread(&(b->a), sizeof(unsigned char), 1, input);
		if (count == 1) 
			{
			count += fread(&(b->b), sizeof(unsigned char), 1, input);
			if (count == 2)
				count += fread(&(b->c), sizeof(unsigned char), 1, input);
			}
	} else
		return -1;
	return count;
}

/*  Funcion que recibe un caracter de seis bits y lo codifica
	teniendo en cuenta la especificacion RFC 2045. */
char get_encoding(unsigned char byte) 
{
	if (byte >= 0 && byte <= 25)
		return (char) (byte + 65);
	if (byte >= 26 && byte <= 51)
		return (char) (byte + 71);
	if (byte >= 52 && byte <= 61)
		return (char) (byte - 4);
	if (byte == 62)
		return (char) 43;
	if (byte == 63)
		return (char) 47;
	return byte;
}

/*  Funcion que recibe bits de a uno y lo almacena temporalmente
	en el buffer. Cuando se tienen seis bits, se los escriben al
	archivo de salida, verificando que la longitud de cada linea
	sea menor a 76 bytes. */
void push_bit_to_buffer(buffer* buff, unsigned char bit, 
		FILE* output) 
{
	buff->buffer = buff->buffer << 1;
	if (bit)
		buff->buffer = buff->buffer | 1;
	buff->index++;
	if(buff->index == 6)
	{
		char c = get_encoding(buff->buffer);
		fwrite(&c, sizeof(unsigned char), 1, output);
		init_buffer(buff);
	}
}

/*  Funcion que recibe un byte y lo procesa junto con la funcion
	push_bit_to_buffer. */
void write_to_buffer(buffer* buff, unsigned char byte,
		unsigned int initialBit, unsigned int finalBit, FILE* output) 
{
	unsigned char aux, i;
	unsigned char index = 1 << (7 - initialBit); 
	
	for(i = finalBit; i > 0; i--) 
	{
		aux = byte & index;
		index = index >> 1;
		push_bit_to_buffer(buff, aux, output);
	}
}

/*  Escribe un caracter de padding al archivo de salida. */
void push_padding(FILE* output)
{
	fwrite(PADDING_STR, sizeof(unsigned char), 1, output);
}

/*  La funcion principal para la tarea de codificar archivos.
	Se llamara a la funcion read_block para recorrer el archivo
	de entrada hasta el final, escribiendo el archivo de salida
	con la funcion write_to_buffer. */
int encode_file(FILE* input, FILE* output)
{
	buffer buff;
	encode_block block;
	int count, blocks_count = 0;

	while((count = read_from_input(&block, input)) == 3)
	{
		init_buffer(&buff);
		write_to_buffer(&buff, block.a, 0, 8, output);
		write_to_buffer(&buff, block.b, 0, 8, output);
		write_to_buffer(&buff, block.c, 0, 8, output);
		blocks_count++;
		/* Si llegamos a los 76 caracteres por linea...*/
		if(blocks_count % 19 == 0)
			fwrite("\n", sizeof(unsigned char), 1, output);
	}
	/* Si se leyeron solo 2 caracteres...*/
	if(count == 2)
	{
		init_buffer(&buff);
		write_to_buffer(&buff, block.a, 0, 8, output);
		write_to_buffer(&buff, block.b, 0, 8, output);
		write_to_buffer(&buff, 0, 0, 8 - 6, output);
		push_padding(output);
		blocks_count++;
	} 
	/* Si se leyo solo 1 caracter...*/
	else if(count == 1)
	{
		init_buffer(&buff);
		write_to_buffer(&buff, block.a, 0, 8, output);
		write_to_buffer(&buff, 0, 0, 8 - 4, output);
		push_padding(output);
		push_padding(output);
		blocks_count++;
	} 

	if(blocks_count % 19 != 0 || count % 3 != 0)
		fwrite("\n", sizeof(unsigned char), 1, output);

	return RES_OK;
}
